#include "mpool.h"

#ifdef _WIN32
/// <summary>
/// 检查对齐的align值是否是2的n次方
/// </summary>
/// <param name="align"></param>
/// <returns></returns>
static int check_align(size_t align)
{
    for (size_t i = sizeof(void *); i != 0; i *= 2)
        if (align == i)
            return 0;
    return EINVAL;
}

/// <summary>
/// 分配大块内存
/// </summary>
/// <param name="ptr">(待修改的)一级指针变量地址</param>
/// <param name="align">对齐值，必须为2的n次方</param>
/// <param name="size">分配内存大小</param>
/// <returns></returns>
int posix_memalign(void **ptr, size_t align, size_t size)
{
    if (check_align(align))
        return EINVAL;

    int saved_errno = errno;
    void *p = _aligned_malloc(size, align);
    /*_aligned_free();*/
    if (p == NULL)
    {
        errno = saved_errno;
        return ENOMEM;
    }

    *ptr = p;
    return 0;
}
#endif

void posix_memalign_free(void *p)
{
#ifdef _WIN32
    _aligned_free(p);
#else
    free(p);
#endif
}

/// @brief 小块内存信息头 放到小块内存里面
typedef struct mpool_node_st
{
    unsigned char *end;         // 4k块的结尾
    unsigned char *last;        // 最后使用的（大/小）块的尾部地址
    struct mpool_node_st *next; // 单链表
    uint32_t quote;             // 引用计数
    uint32_t failed;            // 失效次数
} mpool_node_t;

/// @brief 大块内存信息头 游离的数据
typedef struct mpool_bulk_st
{
    struct mpool_bulk_st *next; // 单链表
    uint32_t size;              // 该内存块大小
    void *alloc;                // 大内存块地址
} mpool_bulk_t;

/// @brief 内存池 放到第一块小块内存里面
struct mpool_st
{
    size_t size;         // 每页大小，也是用来判断是大块内存块还是小块内存块的依据
    mpool_bulk_t *bulks; // 大块内存信息头
    mpool_node_t *nodes; // 小块内存信息头
    mpool_node_t *cur;   // 当前所在4k块小块信息头（当超过一定次数分配不出内存，则会指向下一个块）
};

// 内存对齐
#define mpool_align(n, alignment) (((n) + (alignment - 1)) & ~(alignment - 1))
#define mpool_align_ptr(p, alignment) (void *)((((size_t)p) + (alignment - 1)) & ~(alignment - 1))

static const size_t mpool_page_size = 4096;
static const size_t mpool_align_size = 16;

/// @brief 创建内存池
/// @param size mpool_page_size的倍数 小块内存块大小(字节)
/// @return mpool_t *变量
mpool_t *mpool_init(size_t size)
{
    mpool_t *mpool;
    // 小于4k或者不是4k的倍数 通通强制设置为4k
    if (size < mpool_page_size || size % mpool_page_size != 0)
        size = mpool_page_size;
    int ret = posix_memalign((void **)&mpool, mpool_align_size, size);
    if (ret != 0)
        return NULL;
    mpool->bulks = NULL;
    mpool->size = size;
    mpool->nodes = (unsigned char *)mpool + sizeof(mpool_t);
    mpool->cur = mpool->nodes;
    mpool->nodes->last = (unsigned char *)mpool + sizeof(mpool_t) + sizeof(mpool_node_t);
    mpool->nodes->end = (unsigned char *)mpool + size;
    mpool->nodes->next = NULL;
    mpool->nodes->failed = 0;
    mpool->nodes->quote = 0;
    return mpool;
}

/// @brief 释放内存池
/// @param mpool mpool_t *变量地址
void mpool_deinit(mpool_t **mpool)
{
    if ((*mpool) == NULL)
        return;
    // 释放所有的大块内存
    mpool_bulk_t *bulk, *bulk_next;
    bulk = (*mpool)->bulks;
    while (bulk != NULL)
    {
        bulk_next = bulk->next;
        if (bulk->alloc)
            posix_memalign_free(bulk->alloc);
        // free(bulk);
        posix_memalign_free(bulk);
        bulk = bulk_next;
    }

    mpool_node_t *node, *node_next;
    node = (*mpool)->nodes->next; // 第一个node在 posix_memalign的时候已经分配了 直接跟mpool绑一起了
    while (node != NULL)
    {
        node_next = node->next;
        posix_memalign_free(node);
        node = node_next;
    }
    posix_memalign_free((*mpool));
    *mpool = NULL;
}

/// @brief 往block中占用一块大块内存信息头 并处理改信息头的数据 返回用户申请的内存的起始地址
/// @param mpool
/// @param size
/// @return
static void *__mpool_malloc_bulk(mpool_t *mpool, size_t size)
{
    unsigned char *bigaddr;
    int ret = posix_memalign((void **)&bigaddr, mpool_align_size, size);
    if (ret != 0)
        return NULL;
    mpool_bulk_t *bulk;
    int i = 0;
    for (bulk = mpool->bulks; bulk != NULL; bulk = bulk->next)
    {
        if (bulk->alloc == NULL)
        {
            bulk->alloc = bigaddr;
            bulk->size = size;
            return bigaddr;
        }
        // 尝试次数大于3次则放弃查找
        if (i++ > 3)
            break;
    }
    // bulk = malloc(sizeof(mpool_bulk_t));
    ret = posix_memalign((void **)&bulk, mpool_align_size, sizeof(mpool_bulk_t));
    // if (bulk == NULL)
    if (ret != 0)
    {
        posix_memalign_free(bigaddr);
        bigaddr = NULL;
        goto fail0;
    }
    bulk->size = size;
    bulk->alloc = bigaddr;
    // 把新的插入到头
    bulk->next = mpool->bulks;
    mpool->bulks = bulk;

fail0:
    return bigaddr;
}

/// @brief 创建新的小内存块存储块,并偏移size(匀出用户需要的空间)
/// @param mpool
/// @param size 用户申请的小块内存大小
/// @return
static void *mpool_malloc_block(mpool_t *mpool, size_t size)
{
    unsigned char *block;
    int ret = posix_memalign((void **)&block, mpool_align_size, mpool->size);
    if (ret != 0)
        return NULL;
    mpool_node_t *node = (mpool_node_t *)block;
    node->end = block + mpool->size; // 起始地址+size
    node->next = NULL;
    node->failed = 0;
    node->quote = 0;

    unsigned char *retaddr = mpool_align_ptr(block + sizeof(mpool_node_t), mpool_align_size);
    node->last = retaddr + size; // 偏移到用户的数据后面
    node->quote++;

    mpool_node_t *cur = mpool->cur;
    // 用游标mpool->cur 能更快找到最后的节点
    while (cur->next != NULL)
    {
        if (cur->failed++ > 4)
            mpool->cur = cur;
        cur = cur->next;
    }
    cur->next = node;
    return retaddr;
}

/// @brief 外部函数 在mpool 内存池中分配地址
/// @param mpool mpool_t 变量地址
/// @param size 分配内存大小
/// @return
void *mpool_malloc(mpool_t *mpool, size_t size)
{
    if (mpool == NULL)
        return NULL;
    // 大块内存
    if (size > mpool->size - sizeof(mpool_node_t))
        return __mpool_malloc_bulk(mpool, size);
    // 小块内存
    unsigned char *minaddr;
    mpool_node_t *cur;
    cur = mpool->cur;
    while (cur)
    {
        // 计算对齐后的地址
        minaddr = mpool_align_ptr(cur->last, mpool_align_size);
        size_t diffsize = cur->end - minaddr;
        if (diffsize >= size)
        {
            cur->quote++;
            cur->last = minaddr + size; // last地址后移
            return minaddr;
        }
        cur = cur->next;
    }
    return mpool_malloc_block(mpool, size);
}

/// @brief 释放用户的内存
/// @param mpool mpool_t *变量
/// @param p 用户的指针（分配给用户的地址）
void mpool_free(mpool_t *mpool, void *p)
{
    if (mpool == NULL || p == NULL)
        return;
    // 大块内存走链表
    mpool_bulk_t *bulk;
    for (bulk = mpool->bulks; bulk != NULL; bulk = bulk->next)
    {
        if (bulk->alloc == p)
        {
            // free(bulk->alloc);
            posix_memalign_free(bulk->alloc);
            bulk->alloc = NULL;
            bulk->size = 0;
        }
    }
    // 小块内存走block,不要释放只需移动指针last指针 以及mpool的cur指针即可
    mpool_node_t *cur;
    for (cur = mpool->nodes; cur != NULL; cur = cur->next)
    {
        if ((unsigned char *)cur <= (unsigned char *)p && (unsigned char *)p <= (unsigned char *)cur->end)
        {
            cur->quote--;
            if (cur->quote == 0)
            {
                if (cur == mpool->nodes)
                    mpool->nodes->last = (unsigned char *)mpool + sizeof(mpool_t) + sizeof(mpool_node_t);
                else
                    cur->last = (unsigned char *)cur + sizeof(mpool_node_t);
            }
            cur->failed = 0;
            // 重置游标 为刚刚清理完的内存块
            // mpool->cur = mpool->nodes;
            mpool->cur = cur;
            return;
        }
    }
}

/// @brief 全局内存池
static mpool_t *global_mpool = NULL;

/// @brief 初始化全局内存池,并将释放函数挂入钩子函数
/// @param size
/// @return 成功:0 已初始化完成:1 内存不足:-1
int8_t global_mpool_init(size_t size)
{
    if (global_mpool != NULL)
        return 1;
    global_mpool = mpool_init(size);
    if (global_mpool == NULL)
        return -1;
    // 挂载销毁全局内存池函数
    atexit(global_mpool_deinit);
    return 0;
}

/// @brief 释放全局内存池
void global_mpool_deinit()
{
    if (global_mpool == NULL)
        return;
    mpool_deinit(&global_mpool);
}

/// @brief 从全局内存池中分配size字节空间
/// @param size
/// @return 地址
void *global_mpool_malloc(size_t size)
{
    return global_mpool == NULL ? malloc(size) : mpool_malloc(global_mpool, size);
}

/// @brief 释放冲全局内存池中分配的空间
/// @param p 地址
void global_mpool_free(void *p)
{
    if (global_mpool == NULL)
    {
        if (p != NULL)
            free(p);
    }
    else
    {
        mpool_free(global_mpool, p);
    }
}
